/*******************************************************************************
 * Copyright (c) 2003 David Corbin and others. All rights reserved. This program
 * and the accompanying materials are made available under the terms of the
 * Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: David Corbin - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui;


import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.resources.LocalResource;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;

import java.util.Collection;
import java.util.Map;
import java.util.Set;


public class NewFilesDialog extends Dialog {
  public interface ResourceViewer {
    public void addFilter(ViewerFilter filter);

    public StructuredViewer getViewer();

    public IResource[] getCheckedElements();

    public void clearAllChecks(boolean clearFlag);
  }
  private class ResourceTable implements ResourceViewer {
    private CheckboxTableViewer viewer;

    public ResourceTable(Composite parent) {
      final Table table =
          new Table(parent, SWT.CHECK | SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
      viewer = new CheckboxTableViewer(table);
      viewer.setContentProvider(new IStructuredContentProvider() {
        private void addResource(Set<IResource> set, IResource resource) throws CoreException {
          if (resource.getType() == IResource.FILE) {
            set.add(resource);
          } else if (resource instanceof IContainer) {
            IResource[] members = ((IContainer) resource).members();
            for (int i = 0; i < members.length; i++)
              addResource(set, members[i]);
          }

        }

        public Object[] getElements(Object inputElement) {
          Set<IResource> set = Sets.newHashSet();
          try {
            if (inputElement instanceof Collection) {
              @SuppressWarnings("unchecked")
              Collection<IResource> c = (Collection<IResource>) inputElement;
              for (IResource resource : c) {
                addResource(set, resource);
              }
            } else if (inputElement instanceof IWorkspaceRoot) {
              addResource(set, (IResource) inputElement);
            }

          } catch (CoreException e) {
            PerforceUIPlugin.handle(e);
          }

          return set.toArray(new IResource[set.size()]);
        }

        public void dispose() {
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }
      });
      viewer.setLabelProvider(new ILabelProvider() {
        private WorkbenchLabelProvider provider = new WorkbenchLabelProvider();

        public String getText(Object element) {
          IResource resource = (IResource) element;
          return resource.getFullPath().toString();
        }

        public void addListener(ILabelProviderListener listener) {
          provider.addListener(listener);
        }

        public void dispose() {
          provider.dispose();
        }

        public boolean isLabelProperty(Object element, String property) {
          return provider.isLabelProperty(element, property);
        }

        public void removeListener(ILabelProviderListener listener) {
          provider.removeListener(listener);

        }

        public Image getImage(Object element) {
          return provider.getImage(element);
        }
      });


      table.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent event) {
          if (event.character == ' ') {
            int index = table.getSelectionIndex();
            TableItem item = table.getItem(index);

            boolean selection = item.getChecked();
            TableItem[] items = table.getSelection();
            for (int i = 0; i < items.length; i++) {
              items[i].setChecked(selection);
            }
          }
        }
      });

      GridData data = new GridData();
      data.grabExcessHorizontalSpace = true;
      data.grabExcessVerticalSpace = true;
      data.widthHint = 500;
      data.heightHint = 200;
      viewer.getControl().setLayoutData(data);

    }

    public void addFilter(ViewerFilter filter) {
      viewer.addFilter(filter);
    }

    public StructuredViewer getViewer() {
      return viewer;
    }

    public IResource[] getCheckedElements() {
      Set<IResource> set = Sets.newHashSet();
      for (int i = 0; i < viewer.getTable().getItemCount(); i++) {
        TableItem item = viewer.getTable().getItem(i);
        if (item.getChecked()) set.add((IResource) item.getData());
      }
      return set.toArray(new IResource[set.size()]);
    }

    public void clearAllChecks(boolean clearFlag) {
      for (int i = 0; i < viewer.getTable().getItemCount(); i++) {
        TableItem item = viewer.getTable().getItem(i);
        item.setChecked(!clearFlag);
      }
    }

    public void setLabelProvider(ILabelProvider provider) {
      viewer.setLabelProvider(provider);

    }

  }

  @SuppressWarnings("unused")
  // TODO(rcleveng): Remove this?
  private class ResourceTree implements ResourceViewer {
    CheckboxTreeViewer viewer;

    public void addFilter(ViewerFilter filter) {
      viewer.addFilter(filter);
    }

    public ResourceTree(Composite container) {
      Tree tree = new Tree(container, SWT.CHECK | SWT.MULTI | SWT.BORDER);
      viewer = new CheckboxTreeViewer(tree);
      viewer.setContentProvider(new WorkbenchContentProvider());
      viewer.addCheckStateListener(new ICheckStateListener() {
        public void checkStateChanged(CheckStateChangedEvent event) {
          handleCheckState(event);
        }
      });
      viewer.addTreeListener(new ITreeViewerListener() {
        public void treeCollapsed(TreeExpansionEvent event) {
        }

        public void treeExpanded(TreeExpansionEvent event) {
          IResource resource = (IResource) event.getElement();
          if (resource instanceof IContainer) {
            IContainer container = (IContainer) resource;
            if (viewer.getGrayed(resource)) return;
            boolean checked = viewer.getChecked(resource);

            setChildrenChecks(container, checked);
          }

        }

      });
      viewer.setLabelProvider(new WorkbenchLabelProvider());

      GridData data = new GridData();
      data.grabExcessHorizontalSpace = true;
      data.grabExcessVerticalSpace = true;
      data.widthHint = 300;
      data.heightHint = 400;
      viewer.getControl().setLayoutData(data);
    }

    public StructuredViewer getViewer() {
      return viewer;
    }

    private void handleCheckState(CheckStateChangedEvent event) {
      IResource resource = (IResource) event.getElement();
      boolean state = event.getChecked();

      viewer.setGrayed(resource, false);
      if (resource instanceof IContainer) {
        setSubtreeChecked((IContainer) resource, state, true);
      }
      updateParentState(resource);


    }

    private void setSubtreeChecked(IContainer container, boolean state, boolean checkExpandedState) {
      // checked state is set lazily on expand, don't set it if container is
      // collapsed
      // if (container.isAccessible() == false ||
      // (viewer.getExpandedState(container) == false && state &&
      // checkExpandedState))
      // {
      // return;
      // }
      IResource[] members = null;
      try {
        members = container.members();
      } catch (CoreException ex) {
        handleCoreException(ex, getShell(), "Error", "Problem checking child elements");
      }
      for (int i = members.length - 1; i >= 0; i--) {
        IResource element = members[i];
        boolean elementGrayChecked = viewer.getGrayed(element) || viewer.getChecked(element);

        if (state) {
          viewer.setChecked(element, true);
          viewer.setGrayed(element, false);
        } else {
          viewer.setGrayChecked(element, false);
        }
        // unchecked state only needs to be set when the container is
        // checked or grayed
        if (element instanceof IContainer && (state || elementGrayChecked)) {
          setSubtreeChecked((IContainer) element, state, true);
        }
      }
    }

    /**
     * Check and gray the resource parent if all resources of the parent are
     * checked.
     * 
     * @param child the resource whose parent checked state should be set.
     */
    private void updateParentState(IResource child) {
      if (child == null || child.getParent() == null) return;

      IContainer parent = child.getParent();
      boolean childChecked = false;
      IResource[] members = null;
      try {
        members = parent.members();
      } catch (CoreException ex) {
        handleCoreException(ex, getShell(), "Error", "Problem updating parent state");

      }
      boolean hasChecked = false;
      boolean hasUnchecked = false;
      for (int i = members.length - 1; i >= 0; i--) {
        if (!needsAddingFilter.select(viewer, parent, members[i])) continue;

        if (viewer.getChecked(members[i])) {
          hasChecked = true;
        } else {
          hasUnchecked = true;
        }
        if (hasChecked && hasUnchecked) break;
      }
      viewer.setGrayChecked(parent, hasChecked == hasUnchecked);
      viewer.setChecked(parent, hasChecked);
      updateParentState(parent);
    }

    public void clearAllChecks(boolean clearFlag) {
      ViewerFilter[] filter = viewer.getFilters();
      Object[] roots =
          ((ITreeContentProvider) viewer.getContentProvider()).getElements(viewer.getInput());
      for (int i = 0; i < roots.length; i++) {
        viewer.setChecked(roots[i], !clearFlag);
        for (int f = 0; f < filter.length; f++) {
          if (filter[f].select(viewer, null, roots[i])) {
            setChildrenChecks((IContainer) roots[i], !clearFlag);
          }
        }

      }
    }

    private void setChildrenChecks(IContainer container, boolean checked) {
      IResource[] members;
      try {
        members = container.members();
        for (int i = 0; i < members.length; i++) {
          viewer.setChecked(members[i], checked);
          if (members[i] instanceof IContainer)
            setChildrenChecks(((IContainer) members[i]), checked);
        }
      } catch (CoreException e) {
        handleCoreException(e, getShell(), "Error", "Problem expanding parent node");
      }
    }

    public IResource[] getCheckedElements() {
      Object[] data = viewer.getCheckedElements();
      Set<IResource> resources = Sets.newHashSet();
      for (Object o : data) {
        IResource resource = (IResource) o;
        if (resource.getType() == IResource.FILE) {
          resources.add(resource);
        }

      }
      return resources.toArray(new IResource[resources.size()]);
    }

  }

  private static int ID_SELECT_ALL = 100;
  private static int ID_CLEAR_ALL = 101;

  private class ResourceNeedsAddingFilter extends ViewerFilter {
    private Map<IResource, Boolean> cache = Maps.newHashMap();

    @Override
    public boolean select(Viewer viewer, Object parentElement, Object element) {
      return resourceNeedsAdding((IResource) element);
    }

    private boolean resourceNeedsAdding(IResource resource) {
      Boolean b = cache.get(resource);
      if (b == null) {
        if (resource instanceof IContainer) {
          try {
            b = new Boolean(containerHasResourceNeedingAdd(resource));
          } catch (CoreException e) {
            b = Boolean.FALSE;
          }
        } else {
          b = new Boolean(LocalResource.resourceNeedsAdding(resource));
        }
        cache.put(resource, b);
      }
      return b.booleanValue();
    }

    private boolean containerHasResourceNeedingAdd(final IResource container) throws CoreException {
      final boolean[] scanResult = {false};
      container.accept(new IResourceVisitor() {
        public boolean visit(IResource resource) {
          if (container == resource) {
            return true;
          }
          if (resourceNeedsAdding(resource)) {
            scanResult[0] = true;
            return false;
          }
          return true;
        }
      }, IResource.DEPTH_ONE, false);
      return scanResult[0];
    }
  }
  private class PerforceProjectsFilter extends ViewerFilter {
    @Override
    public boolean select(Viewer viewer, Object parentElement, Object element) {
      IProject project = ((IResource) element).getProject();

      return RepositoryProvider.getProvider(project, PerforceCorePlugin.getTypeId()) != null;
    }
  }
  private class SelectedResourcesAndChildrenFilter extends ViewerFilter {
    @SuppressWarnings("hiding")
    private IResource[] resources;

    public SelectedResourcesAndChildrenFilter(IResource[] resources) {
      this.resources = resources;
    }

    @Override
    public boolean select(Viewer viewer, Object parentElement, Object element) {
      IPath elementPath = ((IResource) element).getFullPath();
      for (int i = 0; i < resources.length; i++) {
        IPath resourcePath = resources[i].getFullPath();
        if (elementPath.isPrefixOf(resourcePath)) return true;
        if (resourcePath.isPrefixOf(elementPath)) return true;
      }
      return false;
    }
  }

  private IResource[] checkedResources = new IResource[0];
  private IResource[] resources;
  private ResourceNeedsAddingFilter needsAddingFilter;
  private ResourceViewer resourceViewer;

  public NewFilesDialog(Shell parent, IResource[] resources) {
    super(parent);
    this.resources = resources;
  }

  public NewFilesDialog(Shell shell) {
    super(shell);
    this.resources = null;
  }

  @Override
  protected Control createDialogArea(Composite parent) {
    Composite container = new Composite(parent, SWT.NONE);
    GridLayout layout = new GridLayout();
    layout.numColumns = 1;
    container.setLayout(layout);

    new Label(container, SWT.NONE).setText("Files to Add to Source Control:");

    // resourceViewer = new ResourceTree(container);
    resourceViewer = new ResourceTable(container);

    if (resources != null)
      resourceViewer.addFilter(new SelectedResourcesAndChildrenFilter(resources));

    resourceViewer.addFilter(new PerforceProjectsFilter());
    needsAddingFilter = new ResourceNeedsAddingFilter();
    resourceViewer.addFilter(needsAddingFilter);
    resourceViewer.getViewer().setInput(ResourcesPlugin.getWorkspace().getRoot());

    resourceViewer.getViewer().setSorter(new ViewerSorter() {
      @Override
      public int compare(Viewer viewer, Object e1, Object e2) {
        IResource r1 = (IResource) e1;
        IResource r2 = (IResource) e2;

        return r1.getFullPath().toString().compareTo(r2.getFullPath().toString());
      }
    });

    return container;
  }

  @Override
  protected void createButtonsForButtonBar(Composite parent) {
    createButton(parent, ID_SELECT_ALL, "Select All", false);
    createButton(parent, ID_CLEAR_ALL, "Clear All", false);
    super.createButtonsForButtonBar(parent);
  }

  @Override
  protected void buttonPressed(int buttonId) {
    if (buttonId != ID_SELECT_ALL && buttonId != ID_CLEAR_ALL) {
      super.buttonPressed(buttonId);
      return;
    }
    boolean clearFlag = (buttonId == ID_CLEAR_ALL);
    resourceViewer.clearAllChecks(clearFlag);
  }

  private void handleCoreException(CoreException exception, Shell shell, String title,
      String message) {
    IStatus status = exception.getStatus();
    if (status != null) {
      ErrorDialog.openError(shell, title, message, status);
    } else {
      MessageDialog.openError(shell, "InternalError", exception.getLocalizedMessage());
    }
  }

  @Override
  protected void configureShell(Shell newShell) {
    super.configureShell(newShell);
    newShell.setText("Add To Source Control");
  }

  @Override
  protected void okPressed() {
    Object[] data = resourceViewer.getCheckedElements();
    Set<IResource> resources = Sets.newHashSet();
    for (int i = 0; i < data.length; i++) {
      IResource resource = (IResource) data[i];
      if (resource.getType() == IResource.FILE) resources.add(resource);

    }
    checkedResources = resources.toArray(new IResource[0]);
    super.okPressed();
  }

  public IResource[] getSelectedResources() {
    return checkedResources;
  }
}
